package com.ala4.oxcafe.common.utils;

import com.ala4.oxcafe.exception.BusinessException;
import com.ala4.oxcafe.properties.SmsProperties;
import com.aliyun.sms.SMSPutDTO;
import com.aliyun.sms.SMSTemplateEnum;
import jakarta.xml.bind.DatatypeConverter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 阿里云发送短信服务
 *
 * @author PING
 * @version 1.0.0
 * @date 2025/8/13 14:10
 */
@Slf4j
@Component
public class SmsClientUtil {

    /**
     * 日期格式化工具，用于将日期时间字符串格式化为"yyyy-MM-dd'T'HH:mm:ss'Z'"的格式。
     */
    private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
    /**
     * 签名协议
     */
    private static final String ALGORITHM = "ACS3-HMAC-SHA256";
    private static SmsProperties smsProperties;

    public static void sendSms(SMSPutDTO smsPutDTO) {
//        POST /?PhoneNumbers=123****4567&SignName=阿里云短信测试&TemplateCode=SMS_154950909&TemplateParam={"code":"1234"} HTTP/1.1
//        Host: dysmsapi.aliyuncs.com
//        Authorization: ACS3-HMAC-SHA256 Credential=YourAccessKeyId,SignedHeaders=host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version,Signature=06563a9e1b43f5dfe96b81********ceab24a1d853912eee15083a6f0f3283c0
//        x-acs-action: SendSms
//        x-acs-version: 2017-05-25
//        x-acs-signature-nonce: d410180a5abf7f********74aca91fc0
//        x-acs-date: 2024-12-02T06:53:09Z
//        x-acs-content-sha256: e3b0c44298fc1c149afb********b92427ae41e4649b934ca495991b7852b855

        String httpMethod = "POST"; // 请求方式，从元数据中可以获取，建议使用POST。
        String canonicalUri = "/"; // RPC接口无资源路径，故使用正斜杠（/）作为CanonicalURI
        String host = "dysmsapi.aliyuncs.com";  // 云产品服务接入点
        String xAcsAction = "SendSms";  // API名称
        String xAcsVersion = "2017-05-25"; // API版本号
        SignatureRequest signatureRequest = new SignatureRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
        // 发送的手机号
        signatureRequest.queryParam.put("PhoneNumbers", smsPutDTO.getPhoneNumbers());
        // 短信签名名称
        signatureRequest.queryParam.put("SignName", smsPutDTO.getSmsTemplateEnum().getSignName());
        // 短信模板Code
        signatureRequest.queryParam.put("TemplateCode", smsPutDTO.getSmsTemplateEnum().getTemplateCode());
        // 短信模板变量对应的实际值
        signatureRequest.queryParam.put("TemplateParam", smsPutDTO.getTemplateParam());

        // 签名过程
        getAuthorization(signatureRequest);

        try {
            // 调用发送
            String respStr = callApi(signatureRequest);
            if (respStr != null) {
                Map<String, Object> respMap = JSONUtil.toMap(respStr);
                if (respMap.containsKey("Code") && respMap.get("Code").equals("OK")) {
                    log.info("调用发送短信成功:{}", smsPutDTO.getPhoneNumbers());
                } else {
                    log.error("调用发送短信失败:{}", respStr);
                    throw new BusinessException("调用发送短信失败:{}", respMap.get("Message"));
                }
            }
        } catch (BusinessException businessException) {
            throw businessException;
        } catch (InterruptedException e) {
            // 重新设置中断状态
            Thread.currentThread().interrupt();
            log.error(e.getMessage(), e);
            throw new BusinessException("Request interrupted", e);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new BusinessException("调用发送短信出现异常:{}", e.getMessage());
        }
    }

    public static void main(String[] args) {
        SMSPutDTO smsPutDTO = new SMSPutDTO();
        smsPutDTO.setPhoneNumbers("13352798814");
        smsPutDTO.setSmsTemplateEnum(SMSTemplateEnum.CAPTCHA);
        smsPutDTO.setTemplateParam("{\"code\":\"7654\"}");
        SmsClientUtil.sendSms(smsPutDTO);
    }

    /**
     * 签名示例，您需要根据实际情况替换main方法中的示例参数。
     * ROA接口和RPC接口只有canonicalUri取值逻辑是完全不同，其余内容都是相似的。
     * <p>
     * 通过API元数据获取请求方法（methods）、请求参数名称（name）、请求参数类型（type）、请求参数位置（in），并将参数封装到SignatureRequest中。
     * 1. 请求参数在元数据中显示"in":"query"，通过queryParam传参。注：RPC接口该类型参数也支持通过body传参，content-type为application/x-www-form-urlencoded，参见示例三。
     * 2. 请求参数在元数据中显示"in": "body"，通过body传参，MIME类型为application/octet-stream、application/json。
     * 3. 请求参数在元数据中显示"in": "formData"，通过body传参，MIME类型为application/x-www-form-urlencoded。
     * RPC接口不建议使用application/json，可使用示例三替代。
     */
//    public static void main(String[] args) throws IOException {
//        // RPC接口请求示例一：请求参数"in":"query"
//        String httpMethod = "POST"; // 请求方式，从元数据中可以获取，建议使用POST。
//        String canonicalUri = "/"; // RPC接口无资源路径，故使用正斜杠（/）作为CanonicalURI
//        String host = "ecs.cn-hangzhou.aliyuncs.com";  // 云产品服务接入点
//        String xAcsAction = "DescribeInstanceStatus";  // API名称
//        String xAcsVersion = "2014-05-26"; // API版本号
//        SignatureRequest signatureRequest = new SignatureRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
//        // DescribeInstanceStatus请求参数如下：
//        // RegionId在元数据中显示的类型是String，"in":"query"，必填
//        signatureRequest.queryParam.put("RegionId", "cn-hangzhou");
//        // InstanceId的在元数据中显示的类型是array，"in":"query"，非必填
//        String[] instanceIds = {"i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX", "i-bp1incuofvzxXXXXXXXX"};
//        signatureRequest.queryParam.put("InstanceId", Arrays.asList(instanceIds));
//
//        /*// RPC接口请求示例二：请求参数"in":"body"（上传文件场景）
//        String httpMethod = "POST";
//        String canonicalUri = "/";
//        String host = "ocr-api.cn-hangzhou.aliyuncs.com";
//        String xAcsAction = "RecognizeGeneral";
//        String xAcsVersion = "2021-07-07";
//        SignatureRequest signatureRequest = new SignatureRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
//        // 请求参数在元数据中显示"in": "body"，通过body传参。
//        signatureRequest.body = Files.readAllBytes(Paths.get("D:\\test.png"));
//        signatureRequest.headers.put("content-type", "application/octet-stream");*/
//
//        /*// RPC接口请求示例三：请求参数"in": "formData"或"in":"body"（非上传文件场景）
//        String httpMethod = "POST";
//        String canonicalUri = "/";
//        String host = "mt.aliyuncs.com";
//        String xAcsAction = "TranslateGeneral";
//        String xAcsVersion = "2018-10-12";
//        SignatureRequest signatureRequest = new SignatureRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
//        // TranslateGeneral请求参数如下：
//        // Context在元数据中显示的类型是String，"in":"query"，非必填
//        signatureRequest.queryParam.put("Context", "早上");
//        // FormatType、SourceLanguage、TargetLanguage等参数，在元数据中显示"in":"formData"
//        Map<String, Object> body = new HashMap<>();
//        body.put("FormatType", "text");
//        body.put("SourceLanguage", "zh");
//        body.put("TargetLanguage", "en");
//        body.put("SourceText", "你好");
//        body.put("Scene", "general");
//        String formDataToString = formDataToString(body);
//        signatureRequest.body = formDataToString.getBytes(StandardCharsets.UTF_8);
//        signatureRequest.headers.put("content-type", "application/x-www-form-urlencoded");*/
//
//        /*// ROA接口POST请求
//        String httpMethod = "POST";
//        String canonicalUri = "/clusters"; // 从元数据中获取："path": "/clusters"
//        String host = "cs.cn-beijing.aliyuncs.com"; // endpoint
//        String xAcsAction= "CreateCluster"; // API名称
//        String xAcsVersion=  "2015-12-15"; // API版本号
//        SignatureRequest signatureRequest = new SignatureRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
//        // 调用API所需要的参数，请求参数在元数据中显示"in": "body"，表示参数放在body中
//        TreeMap<String, Object> body = new TreeMap<>();
//        body.put("name", "测试");
//        body.put("region_id", "cn-beijing");
//        body.put("cluster_type", "ExternalKubernetes");
//        body.put("vpcid", "vpc-2zeou1uod4ylaXXXXXXXX");
//        body.put("container_cidr","10.0.0.0/8");
//        body.put("service_cidr", "10.2.0.0/24");
//        body.put("security_group_id", "sg-2ze1a0rlgeo7XXXXXXXX");
//        body.put("vswitch_ids", Collections.singletonList(
//                "vsw-2zei30dhfldu8XXXXXXXX"
//        ));
//        Gson gson = (new GsonBuilder()).disableHtmlEscaping().create();
//        signatureRequest.body = gson.toJson(body).getBytes(StandardCharsets.UTF_8);
//        signatureRequest.headers.put("content-type", "application/json");*/
//
//        /*// ROA接口GET请求
//        String httpMethod = "GET";
//        // canonicalUri如果存在path参数，需要对path参数encode，percentCode({path参数})
//        String canonicalUri = "/clusters/" + percentCode("cdb14b4f85130407da748fd3fXXXXXXXX") + "/resources";
//        String host = "cs.cn-beijing.aliyuncs.com"; // endpoint
//        String xAcsAction = "DescribeClusterResources"; // API名称
//        String xAcsVersion = "2015-12-15"; // API版本号
//        SignatureRequest signatureRequest = new SignatureRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
//        signatureRequest.queryParam.put("with_addon_resources", true);*/
//
//        /*// ROA接口DELETE请求
//        String httpMethod = "DELETE";
//        String canonicalUri = "/clusters/" + percentCode("cdb14b4f85130407da748fd3fXXXXXXXX");
//        String host = "cs.cn-beijing.aliyuncs.com";
//        String xAcsAction = "DeleteCluster";
//        String xAcsVersion = "2015-12-15";
//        SignatureRequest signatureRequest = new SignatureRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);*/
//
//        // 签名过程
//        getAuthorization(signatureRequest);
//        // 调用API
//        callApi(signatureRequest);
//    }
    private static String callApi(SignatureRequest signatureRequest) throws IOException, InterruptedException {

        // 构建基础 URI
        StringBuilder uriBuilder = new StringBuilder("https://")
                .append(signatureRequest.host)
                .append(signatureRequest.canonicalUri);

        // 添加查询参数
        if (!signatureRequest.queryParam.isEmpty()) {
            uriBuilder.append("?");
            signatureRequest.queryParam.forEach((key, value) ->
            {
                try {
                    uriBuilder.append(key)
                            .append("=")
                            .append(URLEncoder.encode((String) value, "UTF-8"))
                            .append("&");
                } catch (UnsupportedEncodingException e) {
                    throw new RuntimeException(e);
                }
            });
            // 删除末尾多余的 '&'
            uriBuilder.deleteCharAt(uriBuilder.length() - 1);
        }

        String url = uriBuilder.toString();
        log.info("发送短信的URL:{}", url);

        // 创建请求构建器
        HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
                .uri(URI.create(url));

        // 设置请求方法和请求体 (POST)
        if ("POST".equalsIgnoreCase(signatureRequest.httpMethod) && signatureRequest.body != null) {
            requestBuilder.POST(HttpRequest.BodyPublishers.ofByteArray(signatureRequest.body));
        } else if ("POST".equalsIgnoreCase(signatureRequest.httpMethod)) {
            requestBuilder.POST(HttpRequest.BodyPublishers.noBody());
        } else if ("DELETE".equalsIgnoreCase(signatureRequest.httpMethod)) {
            requestBuilder.DELETE();
        } else if ("GET".equalsIgnoreCase(signatureRequest.httpMethod)) {
            requestBuilder.GET();
        } else {
            throw new IllegalArgumentException("Unsupported HTTP method");
        }
        System.setProperty("jdk.httpclient.allowRestrictedHeaders", "connection,content-length,expect,host,upgrade");

        // 添加请求头
        for (Map.Entry<String, String> entry : signatureRequest.headers.entrySet()) {
            requestBuilder.header(entry.getKey(), entry.getValue());
        }

        // 创建 HttpClient 并发送请求
        try (HttpClient client = HttpClient.newHttpClient()) {
            HttpResponse<String> response = client.send(
                    requestBuilder.build(),
                    HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)
            );
            // 打印响应结果
            log.info("发送短信返回结果:{}", response.body());
            return response.body();
        }

    }

    /**
     * 该方法用于根据传入的HTTP请求方法、规范化的URI、查询参数等，计算并生成授权信息。
     */
    private static void getAuthorization(SignatureRequest signatureRequest) {
        try {
            // 处理queryParam中参数值为List、Map类型的参数，将参数平铺
            TreeMap<String, Object> newQueryParam = new TreeMap<>();
            processObject(newQueryParam, "", signatureRequest.queryParam);
            signatureRequest.queryParam = newQueryParam;
            // 步骤 1：拼接规范请求串
            // 请求参数，当请求的查询字符串为空时，使用空字符串作为规范化查询字符串
            StringBuilder canonicalQueryString = new StringBuilder();
            signatureRequest.queryParam.entrySet().stream().map(entry -> percentCode(entry.getKey()) + "="
                    + percentCode(String.valueOf(entry.getValue()))).forEachOrdered(queryPart -> {
                // 如果canonicalQueryString已经不是空的，则在查询参数前添加"&"
                if (canonicalQueryString.length() > 0) {
                    canonicalQueryString.append("&");
                }
                canonicalQueryString.append(queryPart);
            });

            // 计算请求体的哈希值
            String requestPayload = ""; // 请求体，当请求正文为空时，比如GET请求，RequestPayload固定为空字符串
            String hashedRequestPayload = signatureRequest.body != null ? sha256Hex(signatureRequest.body) : sha256Hex(requestPayload.getBytes(StandardCharsets.UTF_8));
            signatureRequest.headers.put("x-acs-content-sha256", hashedRequestPayload);
            // 构造请求头，多个规范化消息头，按照消息头名称（小写）的字符代码顺序以升序排列后拼接在一起
            StringBuilder canonicalHeaders = new StringBuilder();
            // 已签名消息头列表，多个请求头名称（小写）按首字母升序排列并以英文分号（;）分隔
            StringBuilder signedHeadersSb = new StringBuilder();
            signatureRequest.headers.entrySet().stream().filter(entry -> entry.getKey().toLowerCase().startsWith("x-acs-") || "host".equalsIgnoreCase(entry.getKey()) || "content-type".equalsIgnoreCase(entry.getKey())).sorted(Map.Entry.comparingByKey()).forEach(entry -> {
                String lowerKey = entry.getKey().toLowerCase();
                String value = String.valueOf(entry.getValue()).trim();
                canonicalHeaders.append(lowerKey).append(":").append(value).append("\n");
                signedHeadersSb.append(lowerKey).append(";");
            });
            String signedHeaders = signedHeadersSb.substring(0, signedHeadersSb.length() - 1);
            String canonicalRequest = signatureRequest.httpMethod + "\n" + signatureRequest.canonicalUri + "\n" + canonicalQueryString + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestPayload;
//            System.out.println("canonicalRequest=========>\n" + canonicalRequest);

            // 步骤 2：拼接待签名字符串
            String hashedCanonicalRequest = sha256Hex(canonicalRequest.getBytes(StandardCharsets.UTF_8)); // 计算规范化请求的哈希值
            String stringToSign = ALGORITHM + "\n" + hashedCanonicalRequest;
//            System.out.println("stringToSign=========>\n" + stringToSign);

            // 步骤 3：计算签名
            String signature = DatatypeConverter.printHexBinary(hmac256(smsProperties.getAccessKeySecret().getBytes(StandardCharsets.UTF_8), stringToSign)).toLowerCase();
//            System.out.println("signature=========>" + signature);

            // 步骤 4：拼接 Authorization
            String authorization = ALGORITHM + " " + "Credential=" + smsProperties.getAccessKeyId() + ",SignedHeaders=" + signedHeaders + ",Signature=" + signature;
//            System.out.println("authorization=========>" + authorization);
            signatureRequest.headers.put("Authorization", authorization);
        } catch (Exception e) {
            log.error("短信签名出现异常-Failed to get authorization", e);
            throw new BusinessException("短信签名出现异常-Failed to get authorization", e);
        }
    }

    /**
     * 处理请求参数类型为formData的参数。
     *
     * @param formData formData类型参数
     * @return String
     */
    private static String formDataToString(Map<String, Object> formData) {
        Map<String, Object> tileMap = new HashMap<>();
        processObject(tileMap, "", formData);
        StringBuilder result = new StringBuilder();
        boolean first = true;
        String symbol = "&";
        for (Map.Entry<String, Object> entry : tileMap.entrySet()) {
            String value = String.valueOf(entry.getValue());
            if (value != null && !value.isEmpty()) {
                if (first) {
                    first = false;
                } else {
                    result.append(symbol);
                }
                result.append(percentCode(entry.getKey()));
                result.append("=");
                result.append(percentCode(value));
            }
        }

        return result.toString();
    }

    /**
     * 递归处理对象，将复杂对象（如Map和List）展开为平面的键值对
     *
     * @param map   原始的键值对集合，将被递归地更新
     * @param key   当前处理的键，随着递归的深入，键会带有嵌套路径信息
     * @param value 对应于键的值，可以是嵌套的Map、List或其他类型
     */
    private static void processObject(Map<String, Object> map, String key, Object value) {
        // 如果值为空，则无需进一步处理
        if (value == null) {
            return;
        }
        if (key == null) {
            key = "";
        }
        // 当值为List类型时，遍历List中的每个元素，并递归处理
        if (value instanceof List<?>) {
            List<?> list = (List<?>) value;
            for (int i = 0; i < list.size(); ++i) {
                processObject(map, key + "." + (i + 1), list.get(i));
            }
        } else if (value instanceof Map<?, ?>) {
            // 当值为Map类型时，遍历Map中的每个键值对，并递归处理
            Map<?, ?> subMap = (Map<?, ?>) value;
            for (Map.Entry<?, ?> entry : subMap.entrySet()) {
                processObject(map, key + "." + entry.getKey().toString(), entry.getValue());
            }
        } else {
            // 对于以"."开头的键，移除开头的"."以保持键的连续性
            if (key.startsWith(".")) {
                key = key.substring(1);
            }
            // 对于byte[]类型的值，将其转换为UTF-8编码的字符串
            if (value instanceof byte[]) {
                map.put(key, new String((byte[]) value, StandardCharsets.UTF_8));
            } else {
                // 对于其他类型的值，直接转换为字符串
                map.put(key, String.valueOf(value));
            }
        }
    }

    /**
     * 使用HmacSHA256算法生成消息认证码（MAC）。
     *
     * @param secretKey 密钥，用于生成MAC的密钥，必须保密。
     * @param str       需要进行MAC认证的消息。
     * @return 返回使用HmacSHA256算法计算出的消息认证码。
     * @throws Exception 如果初始化MAC或计算MAC过程中遇到错误，则抛出异常。
     */
    public static byte[] hmac256(byte[] secretKey, String str) throws Exception {
        // 实例化HmacSHA256消息认证码生成器
        Mac mac = Mac.getInstance("HmacSHA256");
        // 创建密钥规范，用于初始化MAC生成器
        SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey, mac.getAlgorithm());
        // 初始化MAC生成器
        mac.init(secretKeySpec);
        // 计算消息认证码并返回
        return mac.doFinal(str.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * 使用SHA-256算法计算字符串的哈希值并以十六进制字符串形式返回。
     *
     * @param input 需要进行SHA-256哈希计算的字节数组。
     * @return 计算结果为小写十六进制字符串。
     * @throws Exception 如果在获取SHA-256消息摘要实例时发生错误。
     */
    public static String sha256Hex(byte[] input) throws Exception {
        // 获取SHA-256消息摘要实例
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        // 计算字符串s的SHA-256哈希值
        byte[] d = md.digest(input);
        // 将哈希值转换为小写十六进制字符串并返回
        return DatatypeConverter.printHexBinary(d).toLowerCase();
    }

    /**
     * 对指定的字符串进行URL编码。
     * 使用UTF-8编码字符集对字符串进行编码，并对特定的字符进行替换，以符合URL编码规范。
     *
     * @param str 需要进行URL编码的字符串。
     * @return 编码后的字符串。其中，加号"+"被替换为"%20"，星号"*"被替换为"%2A"，波浪号"%7E"被替换为"~"。
     */
    public static String percentCode(String str) {
        if (str == null) {
            throw new IllegalArgumentException("输入字符串不可为null");
        }
        try {
            return URLEncoder.encode(str, "UTF-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("UTF-8编码不被支持", e);
        }
    }

    @Autowired
    public void setSmsProperties(SmsProperties smsProperties) {
        SmsClientUtil.smsProperties = smsProperties;
    }

    private static class SignatureRequest {
        // HTTP Method
        private final String httpMethod;
        // 请求路径，当资源路径为空时，使用正斜杠(/)作为CanonicalURI
        private final String canonicalUri;
        // endpoint
        private final String host;
        // API name
        private final String xAcsAction;
        // API version
        private final String xAcsVersion;
        // headers
        TreeMap<String, String> headers = new TreeMap<>();
        // body参数对应的字节数组，请求参数在元数据中显示"in":"body"或"in": "formData"，表示参数放在body中
        byte[] body;
        // query参数，请求参数在元数据中显示"in":"query"，表示参数拼接在请求URL上
        TreeMap<String, Object> queryParam = new TreeMap<>();

        public SignatureRequest(String httpMethod, String canonicalUri, String host, String xAcsAction, String xAcsVersion) {
            this.httpMethod = httpMethod;
            this.canonicalUri = canonicalUri;
            this.host = host;
            this.xAcsAction = xAcsAction;
            this.xAcsVersion = xAcsVersion;
            initHeader();
        }

        // init headers
        private void initHeader() {
            headers.put("host", host);
            headers.put("x-acs-action", xAcsAction);
            headers.put("x-acs-version", xAcsVersion);
            SDF.setTimeZone(new SimpleTimeZone(0, "GMT")); // 设置日期格式化时区为GMT
            headers.put("x-acs-date", SDF.format(new Date()));
            headers.put("x-acs-signature-nonce", UUID.randomUUID().toString());
        }
    }
}
